﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using System.Xml.Schema;
using DataGeneration.Domain.DataAccessLayer;
using DataGeneration.Domain.Helpers;
using DataGeneration.Domain.Logging;

namespace DataGeneration.Domain.Logic
{
    /// <summary>
    /// Defines the type PatternProcessorBase
    /// </summary>
    public abstract class PatternProcessorBase
    {
        private static XmlSchemaSet GenerateSchemaSetFromResources()
        {
            var schema = XmlSchema.Read(Assembly.GetExecutingAssembly()
                                                  .GetManifestResourceStream("DataGeneration.Domain.Resources.DataLoadSchema.xsd"),
                                              null);

            var schemaSet = new XmlSchemaSet();
            schemaSet.Add(schema);
            schemaSet.Compile();

            return schemaSet;
        }

        private static void ValidateDataLoad(XDocument xdocument)
        {
            var foundErrors = false;
            xdocument.Validate(GenerateSchemaSetFromResources(), (sender, vea) =>
                                                                     {
                                                                         foundErrors = true;
                                                                     }, true);

            if (foundErrors)
            {
                var stringBuilder = new StringBuilder();
                DisplayValidationErrors(xdocument.Root, stringBuilder);
                throw new Exception(string.Format("Invalid DataLoad. Errors displayed below:\n{0}", stringBuilder));
            }
        }

        private static void DisplayValidationErrors(XElement element, StringBuilder stringBuilder)
        {
            if (element != null && element.GetSchemaInfo().Validity != XmlSchemaValidity.Valid)
            {
                stringBuilder.AppendFormat("Invalid Element {0}\n",
                                           element.AncestorsAndSelf()
                                               .InDocumentOrder()
                                               .Aggregate("", (s, i) => string.Format("{0}/{1}", s, i.Name)));
            }

            foreach (var attribute in element.Attributes().Where(attribute => attribute != null && attribute.GetSchemaInfo().Validity != XmlSchemaValidity.Valid))
            {
                stringBuilder.AppendFormat("Invalid Attribute {0}\n",
                                           string.Format("{0}/@{1}", attribute.Parent
                                                                         .AncestorsAndSelf()
                                                                         .InDocumentOrder()
                                                                         .Aggregate("", (s, i) => string.Format("{0}/{1}", s, i.Name.ToString())), attribute.Name)
                    );
            }

            foreach (var child in element.Elements())
            {
                DisplayValidationErrors(child, stringBuilder);
            }
        }

        /// <summary>
        /// Gets or sets the data access.
        /// </summary>
        /// <value>The data access.</value>
        protected DataAccess DataAccess { get; set; }

        /// <summary>
        /// Reads the entire column from db.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="columnName">Name of the column.</param>
        /// <returns></returns>
        protected IList<T> ReadEntireColumnFromDb<T>(string tableName, string columnName)
        {
            DataAccess.InitializeCommand(string.Format("SELECT {0} FROM {1}", columnName, tableName), CommandType.Text);
            IList<T> columnData = new List<T>();
            using (IDataReader reader = DataAccess.ExecuteDataReader())
            {
                while (reader.Read())
                {
                    columnData.Add(Typehelper.EnsureType<T>(reader[columnName]));
                }
            }

            return columnData;
        }

        /// <summary>
        /// Initializes the data access.
        /// </summary>
        /// <returns></returns>
        protected static DataAccess InitializeDataAccess()
        {
            DataAccess dataAccess = new DataAccess
                                         {
                                             Connection = DataAccess.OpenConnection(ParameterizedAppSettings.DatabaseConnectionString)
                                         };

            dataAccess.Transaction = dataAccess.Connection.BeginTransaction();
            return dataAccess;
        }

        /// <summary>
        /// Gets the data load patterns.
        /// </summary>
        /// <returns></returns>
        protected static IEnumerable<XElement> GetDataLoadPatterns()
        {
            var xdocument = XDocument.Load(ParameterizedAppSettings.EntitiesFilename);
            ValidateDataLoad(xdocument);
            return xdocument.Descendants(Tokens.Pattern).Select(pattern => pattern);
        }

        /// <summary>
        /// Creates the file repositories.
        /// </summary>
        protected static void CreateFileRepositories(string patternName)
        {
            FileHelper.CreateFolder(string.Format(@"{0}{1}\Keys", ParameterizedAppSettings.DataLoadScriptsFolder, patternName));
            FileHelper.CreateFolder(string.Format(@"{0}{1}\SQL", ParameterizedAppSettings.DataLoadScriptsFolder, patternName));
            FileHelper.CreateFolder(string.Format(@"{0}{1}\CSV", ParameterizedAppSettings.DataLoadScriptsFolder, patternName));
        }

        /// <summary>
        /// Sorts the entities by reference.
        /// </summary>
        /// <param name="patternEntities">The pattern entities.</param>
        /// <returns></returns>
        protected IEnumerable<XElement> SortEntitiesByReference(IEnumerable<XElement> patternEntities)
        {
            Logger.WriteMessage("Sorting entities by reference.");

            IList<XElement> list = patternEntities.ToList();
            int loopCounter = 0;
            int outerCounter = 0;
            while (outerCounter < list.Count)
            {
                bool sorted = false;
                for (int innerCounter = 0; innerCounter < list.Count; innerCounter++)
                {
                    if (innerCounter != outerCounter)
                    {
                        var c = EntityAReferencesEntityB(list[outerCounter], list[innerCounter].ReadAttributeValue(Tokens.EntityName));
                        if (c && outerCounter < innerCounter)
                        {
                            list.Insert(outerCounter, list[innerCounter]);
                            list.RemoveAt(innerCounter + 1);
                            outerCounter = 0;
                            sorted = true;
                            break;
                        }
                    }
                }
                if (!sorted)
                {
                    outerCounter++;
                }

                loopCounter++;
                if (loopCounter == int.MaxValue)
                {
                    //If we have been looping for this long there is probably something wrong so stop sorting
                    //and just try generate the data with what we have got. It may fail but we don't want to lood forever!
                    break;
                }
            }
            Logger.WriteMessage("Completed sorting of entities by reference.");
            return list;
        }

        /// <summary>
        /// Entities the A references entity B.
        /// </summary>
        /// <param name="entityA">The entity A.</param>
        /// <param name="referenceEntity">The reference entity.</param>
        /// <returns></returns>
        protected bool EntityAReferencesEntityB(XContainer entityA, string referenceEntity)
        {
            var c = (from x in entityA.Descendants()
                     where x.ContainsAttributeWithValue(Tokens.References, referenceEntity)
                     select x).Count();
            return c > 0;
        }

        /// <summary>
        /// Extracts the entities without dependency.
        /// </summary>
        /// <param name="patternEntities">The pattern entities.</param>
        /// <returns></returns>
        protected static IEnumerable<XElement> ExtractEntitiesWithoutDependency(IEnumerable<XElement> patternEntities)
        {
            return (from entity in patternEntities
                    let c = (from x in entity.Descendants()
                             where DetermineDependencyCondition(x)
                             select x).Count()
                    where c == 0
                    select entity).ToList();
        }

        /// <summary>
        /// Determines the dependency condition.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        protected static bool DetermineDependencyCondition(XElement element)
        {
            return element.ContainsAttributeWithoutValue(Tokens.References, "");
        }

        /// <summary>
        /// Extracts the foreign key nodes.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        protected static IEnumerable<XElement> ExtractForeignKeyNodes(XContainer entity)
        {
            return from item in entity.Descendants()
                   where DetermineDependencyCondition(item)
                   select item;
        }

    }
}